/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package jxta;

import atributos.ManagerAtributos;
import estadosistema.EstadoGlobal;
import estadosistema.ManagerMensajes;
import estadosistema.Nodos;
import exclusionmutua.Copiar;
import exclusionmutua.ExclusionMutua;
import exclusionmutua.ExclusionMutuaJxta;
import exclusionmutua.ManejadorConexionEM;
import exclusionmutua.Token;
import exclusionmutua.TokenListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jxta.discovery.DiscoveryService;
import net.jxta.id.ID;
import net.jxta.peergroup.PeerGroup;
import net.jxta.pipe.PipeMsgListener;
import net.jxta.pipe.PipeService;
import net.jxta.platform.NetworkManager.ConfigMode;
import net.jxta.protocol.PipeAdvertisement;
import utilidades.ConsultaAdvertisement;
import utilidades.Manejador;
import vista.Inicio;

/**
 *
 * @author kastillo
 */
public class ManagerJXTA {

    private ManagerAtributos mgerAtt;
    private EscucharConsultas escucharConsultas;
    private Configuracion configuracion;
    private PublicarConsultas publicarConsultas;
    private CrearInputPipe crearInputPipe;
    private ServerSocket serverSocket;
    private ServerSocket serverSockEM;
    private SocketClient clientSocket;
    /**HashMap de String(Id de archivo) y exclusion mutua asociado a ese archivo*/
    private Map<String, ExclusionMutua> exclusionMutuaMap;
    private ExclusionMutuaJxta emj;
    private List<PipeAdvertisement> peersEM = new LinkedList<PipeAdvertisement>();
    public static ManagerMensajes historiaMensajes;
    public static EstadoGlobal estadoGlobal;
    private ArrayList<Nodos> nodos;

    public ManagerJXTA(ConfigMode configMode, String nombreInstancia,
            String nombrePeer) {
        configuracion = new Configuracion(configMode, nombreInstancia, nombrePeer);
        configuracion.iniciarJXTA();

        System.out.println("nombre del peer" + configuracion.getNombrePeer());

        System.out.println("nombre " + configuracion.getNetPeerGroup().getPeerName());
        exclusionMutuaMap = new HashMap<String, ExclusionMutua>();
        historiaMensajes = new ManagerMensajes(nombrePeer);
        estadoGlobal = new EstadoGlobal();
        estadoGlobal.setColor("BLANCO");
    }

    /**
     * Inicializa la clase Esclusión mutua Jxta, que es la clase que utiliza el
     * sistema de exclusión mutua para enviar los mensajes.
     */
    public void initializeEMJxta() {
        emj = new ExclusionMutuaJxta(configuracion, this, mgerAtt);
    }

    public void setCarpetaCompartida(String rutaFolder) throws IOException, FileNotFoundException, ClassNotFoundException {
        mgerAtt = new ManagerAtributos(rutaFolder);
    }

    public void escucharConsultas() {
        DiscoveryService discoveryService = configuracion.getDiscovery();
        PipeService pipeService = configuracion.getPipeService();
        PipeAdvertisement pipedeDescarga = configuracion.getPipeDescarga();
        PipeAdvertisement pipeEM = configuracion.getPipeTokenEM();
        String nombrePeer = configuracion.getNombrePeer();
        if (mgerAtt == null) {
            throw new IllegalStateException("No se ha establecido la carpeta compartida");
        }

        escucharConsultas =
                new EscucharConsultas(discoveryService,
                pipeService,
                pipedeDescarga,
                exclusionMutuaMap,
                pipeEM,
                nombrePeer);

        Thread escuchar = new Thread(escucharConsultas);
        escuchar.start();
    }

    public void publicarConsulta(String query, int tipoQuery, PipeMsgListener listenerRespuesta) {

        try {
            pararEscucha(); //aquí hay un posible error.
            PipeAdvertisement pipeAdv = null;
            if (listenerRespuesta == null) {
                pipeAdv = configuracion.getNewPipeAdvertisement();
                System.out.println("====================EXCLUSIÓN MUTUA=======================");
                System.out.println("==========CONSULTANDO OBJETOS EN OTROS PEERS==============");
            } else {
                pipeAdv = configuracion.getPipeTokenEM();
            }
            DiscoveryService discoveryService = configuracion.getDiscovery();
            PipeService pipeService = configuracion.getPipeService();
            System.out.println("Publicando consulta");
            ConsultaAdvertisement queryAdv = new ConsultaAdvertisement();
            queryAdv.setID(ID.nullID);
            queryAdv.setTipoQuery(tipoQuery);
            queryAdv.setQuery(query);
            queryAdv.setPipe(pipeAdv);

            guardarMensaje(query);


            publicarConsultas = new PublicarConsultas(discoveryService, queryAdv);
            Thread publicar = new Thread(publicarConsultas);
            publicar.start();
            //Si es no es null quiere decir que es una petición de SC por lo tanto
            //se asigna otro manipulador de respuesta.
            if (listenerRespuesta == null) {
                crearInputPipe = new CrearInputPipe(pipeService, pipeAdv);
            } else {
                //Este camino se invoca por ExclusionMutuaJxta cuando solicita un token
                crearInputPipe = new CrearInputPipe(pipeService, pipeAdv, listenerRespuesta);
            }
            Thread crearPipe = new Thread(crearInputPipe);
            crearPipe.start();
            Thread.sleep(10000); // para que no responda asi mismo
            escucharConsultas();
        } catch (InterruptedException ex) {
            Logger.getLogger(ManagerJXTA.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    public void publicarConsulta(String query, int tipoQuery) {
        publicarConsulta(query, tipoQuery, null);
    }

    public void servirArchivos() {
        System.out.println("=====================================================");
        System.out.println("================Sirviendo Archivos===================");
        System.out.println("=====================================================");

        PeerGroup peerGroup = configuracion.getNetPeerGroup();
        PipeAdvertisement pipe = configuracion.getPipeDescarga();

        serverSocket = new ServerSocket(peerGroup, pipe);
        (new Thread(serverSocket, "Servidor de Archivos")).start();
    }

    public void escucharObjetosDevueltosEM() {
        System.out.println("=====================================================");
        System.out.println("====Esperando por Archivos Actualizados EM===========");
        System.out.println("=====================================================");

        PeerGroup peerGroup = configuracion.getNetPeerGroup();
        PipeAdvertisement pipe = configuracion.getPipeSubidaEM();

        serverSockEM = new ServerSocket(peerGroup,
                pipe,
                Manejador.RECIBIR,
                ManejadorConexionEM.class.getName(),
                Inicio.RUTA_DEVUELTOS_EM);
        serverSockEM.setIdArchivo(null);
        (new Thread(serverSockEM, "Manejador Objetos Devueltos Exclusión Mutua")).start();
    }

    /**
     * Descarga los archivos luego de realizar las consultas. Utilizado solo
     * para resolver consultas(primera entrega).
     *
     * @param id Id del archivo a descargar
     * @param ruta Lugar donde se descarga el archivo
     */
    public void descargarArchivo(String id, String ruta) {
        descargarArchivo(id, ruta, CrearInputPipe.pipeAdvRemoto);
    }

    /**
     * Este método manipula tanto las peticiones de descarga hechas por consultas o por
     * peticiones de Sección critica. Es un método mas genérico que el anterior, ya que recibe el
     * pipe del nodo donde se debe descargar.
     *
     * @param id Id de la archivo a descargar.
     * @param ruta Lugar donde se va a descargar el archivo.
     * @param pipeDescarga Referencia al nodo desde donde se va a descargar el archivo.
     */
    public void descargarArchivo(String id, String ruta, PipeAdvertisement pipeDescarga) {

        serverSocket.pararServicio();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException ex) {
            Logger.getLogger(ManagerJXTA.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println("=====================================================");
        System.out.println("================Descargando Archivo===================");
        System.out.println("=====================================================");

        PeerGroup peerGroup = configuracion.getNetPeerGroup();
        clientSocket = new SocketClient(id, peerGroup, pipeDescarga, ruta);
        clientSocket.empezar();
        servirArchivos();
    }

    /**
     * Método que verifica si el nodo es el propietario del archivo. En caso de ser
     * el propietario copia el archivo localemente, en caso de no serlo lo descarga
     * desde el archivo propietario.
     * 
     * @param id Id de la archivo a descargar.
     * @param ruta Lugar donde se va a descargar el archivo.
     * @param pipeDescarga Referencia al nodo desde donde se va a descargar el archivo.
     * @param token token de la exclusión mutua.
     */
    public void traerArchivo(String id, String ruta, PipeAdvertisement pipeAdvertisement, String nombreArch) {
        boolean esPropietario = false;
        ExclusionMutua em = exclusionMutuaMap.get(id);
        esPropietario = em.isPropietario();
        if (!esPropietario) {
            descargarArchivo(id, ruta, pipeAdvertisement);
        } else {
            descargarLocal(nombreArch);
        }

    }

    private void descargarLocal(String nombreArch) {
        String extension = nombreArch.substring(nombreArch.lastIndexOf(".") + 1);
        String ruta = null;
        if (extension.equals("mp3") || extension.equals("wav") || extension.equals("wma")) {
            ruta = Inicio.RUTA_AUDIO;
        } else if (extension.equals("mpg") || extension.equals("avi") || extension.equals("mpeg")) {
            ruta = Inicio.RUTA_VIDEO;
        } else if (extension.equals("pdf") || extension.equals("doc") || extension.equals("docx") || extension.equals("txt")) {
            ruta = Inicio.RUTA_TEXTO;
        }
        File origen = new File(ruta + nombreArch);
        File destino = new File(Inicio.RUTA_EM + nombreArch);
        try {
            Copiar.copy(origen, destino);
        } catch (IOException ex) {
            System.out.println("Error al copiar el archivo cuando se esta descargando luego de haber obtenido el token.");
            Logger.getLogger(ManagerJXTA.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void pararEscucha() {
        escucharConsultas.pararEscucha();
    }

    public void buscarPeer() {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    /**
     * Mediante este metodo se realiza la solicitud de utilizar la sección critica
     *
     * @param idDocumento Contiene la referencia al documento que se quiere acceder
     */
    public void requestCS(String idDocumento, String origen, String nodoDestino, TokenListener listener) throws IOException {
        ExclusionMutua em = exclusionMutuaMap.get(idDocumento);

        String color = estadoGlobal.getColor();
//        int numSec = numeroSecuencia(nodoDestino);
        em.requestingCS(listener, 1, origen, color);
    }

    /**
     * Mediante este metodo se informa que se ha terminado de utilizar la sección critca
     * y por tanto esta disponible para ser utilizada por otro nodo.
     *
     * @param idDocumento Contiene la referencia al documento que se liberó
     */
    public void releaseCS(Token token, String ruta) throws IOException {
        System.out.println("=====================================================");
        System.out.println("=====DEVOLVIENDO ARCHIVO EXCLUSION MUTUA=============");
        System.out.println("==========ID ARCHIVO: " + token.getIdDocumento() + "==================");
        System.out.println("==========RUTA: " + ruta + "====================");
        boolean esPropietario = false;
        ExclusionMutua em = exclusionMutuaMap.get(token.getIdDocumento());
        esPropietario = em.isPropietario();
        if (!esPropietario) {
            devolverCSRemoto(token, ruta);
        } else {
            devolverCSLocal(ruta);
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException ex) {
            Logger.getLogger(ManagerJXTA.class.getName()).log(Level.SEVERE, null, ex);
        }
        em.releaseCS();

    }

    private void devolverCSRemoto(Token token, String ruta) throws IOException {
        serverSockEM.pararServicio();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException ex) {
            Logger.getLogger(ManagerJXTA.class.getName()).log(Level.SEVERE, null, ex);
        }

        PeerGroup peerGroup = configuracion.getNetPeerGroup();
        clientSocket = new SocketClient(
                token.getIdDocumento(),
                peerGroup,
                token.getPropietarioSubida(),
                Inicio.RUTA_EM + ruta,
                Manejador.ENVIAR,
                ManejadorConexionEM.class.getName());
        clientSocket.empezar();
        escucharObjetosDevueltosEM();
        //new File(Inicio.RUTA_EM + ruta).delete();
    }

    private void devolverCSLocal(String nombreArch) {
        String extension = nombreArch.substring(nombreArch.lastIndexOf(".") + 1);
        String ruta = null;
        if (extension.equals("mp3") || extension.equals("wav") || extension.equals("wma")) {
            ruta = Inicio.RUTA_AUDIO;
        } else if (extension.equals("mpg") || extension.equals("avi") || extension.equals("mpeg")) {
            ruta = Inicio.RUTA_VIDEO;
        } else if (extension.equals("pdf") || extension.equals("doc") || extension.equals("docx") || extension.equals("txt")) {
            ruta = Inicio.RUTA_TEXTO;
        }
        File origen = new File(Inicio.RUTA_EM + nombreArch);
        File destino = new File(ruta + nombreArch);

        try {
            Copiar.copy(origen, destino);
            origen.delete();
        } catch (IOException ex) {
            System.out.println("Error al copiar el archivo cuando se esta descargando luego de haber obtenido el token.");
            Logger.getLogger(ManagerJXTA.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Metodo que agrega una nueva sección critica al sistema distribuido de
     * Exclusión Mutua. Este metodo generalmente se usa para añadir recursos de
     * otros nodos.
     *
     * @param idDocumento Id del documento el cual se va a manejar con Exclusión Mutua.
     */
    public void addRecursoCritico(String idDocumento, String nodoOrigen) {
        addRecursoCritico(idDocumento, false, nodoOrigen);
    }

    /**
     *
     * @param idDocumento Id del documento el cual se va a manejar con Exclusión Mutua.
     * @param propietario Informa si este nodo es el propietario.
     * Si es el propietario ademas de añadir el recurso critico se crea el token asociado a ese recurso.
     */
    public void addRecursoCritico(String idDocumento, boolean propietarioBool, String nodoOrigen) {
        if (exclusionMutuaMap.containsKey(idDocumento)) {
            System.out.println("Ignorando: El documento [id = " + idDocumento + "] ya exsite en la exlusion mutua");
            return;
            //throw new IllegalArgumentException("El documento [id = " + idDocumento + "] ya exsite en la exlusion mutua");
        }
        System.out.println("Cargando en Exclusion Mutua: El documento [id = " + idDocumento + "] ya exsite en la exlusion mutua");
        //PipeAdvertisement thisNodePipe = configuracion.getPipeDescarga();
        PipeAdvertisement thisNodePipe = configuracion.getPipeTokenEM();
        PipeAdvertisement propDescarga = null;
        PipeAdvertisement propSubida = null;
        if (propietarioBool) {
            propDescarga = configuracion.getPipeDescarga();
            propSubida = configuracion.getPipeSubidaEM();
        }
        exclusionMutuaMap.put(idDocumento,
                new ExclusionMutua(idDocumento, thisNodePipe, emj, propDescarga, propSubida, nodoOrigen));
    }

    /**
     * Asigna un nuevo peer al vector RN de todas las exclusiones mutuas que se tienen.
     *
     * @param pipeRemoto Peer que se va a añadir.
     */
    public void addPeerRemotoEM(PipeAdvertisement pipeRemoto) {
        for (Map.Entry<String, ExclusionMutua> entrySet : exclusionMutuaMap.entrySet()) {
            entrySet.getValue().addPeerRemoto(pipeRemoto);
        }
    }

    /**
     *  Asigna este peer al vector RN de todas las exclusiones mutuas que se tienen.
     */
    public void addThisPeerEM() {
        for (Map.Entry<String, ExclusionMutua> entrySet : exclusionMutuaMap.entrySet()) {
            //entrySet.getValue().addPeerRemoto(configuracion.getPipeDescarga());
            entrySet.getValue().addPeerRemoto(configuracion.getPipeTokenEM());
        }
    }

    /**
     * Método que verifica si el token se encuentra en este nodo para un documento determinado.
     * @param idDocumento El documento relacionado al token
     * @return Verdadero si el token esta en este nodo, false si el token no esta en este nodo.
     */
    public boolean estaTokenEM(String idDocumento) {
        return (getTokenEM(idDocumento) == null) ? false : true;
    }

    /**
     * Método que devuelve el token relacionado a un documento.
     * @param idDocumento El documento relacionado al token.
     * @return El token en caso de tener el token, null en caso de no tener el token.
     */
    public Token getTokenEM(String idDocumento) {
        ExclusionMutua em = exclusionMutuaMap.get(idDocumento);
        return em.getToken();
    }

    /**
     * Método que recibe un nombre de un nodo y devuelve los doucmentos que ese
     * nodo ha descargado.
     *
     * @param nombreNodo El nombre del nodo.
     *
     * @return Map que relaciona el numero de documento y el numero de veces que el
     * nodo ha descargado dicho documento.
     */
    public Map<String, Integer> getNoVecesCargado(String nombreNodo) {
        Map<String, Integer> resultados = new HashMap<String, Integer>();
        ArrayList<ExclusionMutua> emDeNodo = new ArrayList<ExclusionMutua>();
        for (Map.Entry<String, ExclusionMutua> entrySet : exclusionMutuaMap.entrySet()) {
            ExclusionMutua em = entrySet.getValue();
            if (nombreNodo.equals(em.getNodoOrigen())) {
                emDeNodo.add(em);

            }
        }
        for(ExclusionMutua em : emDeNodo){

        }
        return resultados;
    }

    public Map<String, ExclusionMutua> getExclusionMutuaMap() {
        return exclusionMutuaMap;
    }

    public void recibirMensaje(int secuencia, String nodoOrigen, String mensaje, String color) {
        historiaMensajes.guardarMensajeRecibido(secuencia, nodoOrigen, mensaje, color);
    }

    public void cargarNodos(ArrayList<Nodos> nodos) {
        this.nodos = nodos;
    }

    private void guardarMensaje(String query) {
        if (!query.equals("*")) {
            for (Nodos n : nodos) {
                int secuencia = n.getSecuencia();
                String destino = n.getNombre();
                String color = estadoGlobal.getColor();
                historiaMensajes.guardarMensajeEnviado(secuencia, destino, query, color);
            }
        }
    }

    private int numeroSecuencia(String nombreNodoDestino) {
        int secuencia = 0;
        for (Nodos n : nodos) {
            String destino = n.getNombre();
            if (nombreNodoDestino.equals(destino)) {
                secuencia = n.getSecuencia();
            }
        }

        return secuencia;
    }

    public void limpiarFolderEM() {
        File folder = new File(Inicio.RUTA_EM);

    }
}
